<?php
/**
 * ActiveRecord Model.
 *
 * @package    XDao Framework
 * @author     Cristian Buda <hello@phalien.com>
 * @author     Bogdan Stanescu <bstanescu@gmail.com>
 * @copyright  (c) 2011
 */
abstract class XDao_Model
{
	// Keeps a temporary cache with table schemas
	protected $schemas = array();
    // table_name
    protected $table;

	// value object class name
    protected $vo;

	// column name prefix
    protected $prefix = '';
	// db link
	protected $db;
	// relationships between this table and others
	protected $relations;

	// last error reported
	private $error;

	// error ids
	const ERR_TABLE_NOT_SET            = 1;
	const ERR_COULD_NOT_FETCH_SCHEMA   = 2;
	const ERR_PRIMARY_KEY_NOT_DETECTED = 3;
	const ERR_COLUMN_NON_EXISTING      = 4;
	const ERR_ARRAY_PAIR_EXPECTED      = 5;
	const ERR_ARRAY_EXPECTED           = 6;

	// error messages
	public static $ERROR_MESSAGES = array(
		self::ERR_TABLE_NOT_SET            => 'Table name not set',
		self::ERR_COULD_NOT_FETCH_SCHEMA   => 'Could not fetch table info for table ',
		self::ERR_PRIMARY_KEY_NOT_DETECTED => 'Could not detect primary field for table ',
		self::ERR_COLUMN_NON_EXISTING      => 'Non-existing column ',
		self::ERR_ARRAY_PAIR_EXPECTED      => 'Value must be an array with 2 elements for BETWEEN operations',
		self::ERR_ARRAY_EXPECTED           => 'Value must be an array for IN operations',
		
	);
	
	public function __construct($dbLink = null)
	{
		if (empty($dbLink))
		{
			$this->db = XDb::getInstance();
		}
		else
		{
			$this->db = $dbLink;
		}
	}
	
	public static function trigger_error($error_code, $value = '')
	{
		trigger_error(self::$ERROR_MESSAGES[$error_code] . $value, E_USER_ERROR);
	}
	

	/**
	 * Fetch one or more items.
	 *
	 * @example  $ar = new Companies; $company = $ar->fetch(1);
	 * @example  $ar = new Companies; $companies = $ar->fetch(array(1,2));
	 *
	 * @param   mixed  single ID or array of IDs
	 * @return  mixed
	 */
	public function fetch($id)
	{
		if (is_array($id))
		{
			return $this->find()->where($this->schema()->primary, 'IN', $id)->all();
		}
		else
		{
			$item = $this->fetch_new();
			if ($item->load($id))
			{
				return $item;
			}
		}
		return FALSE;
	}
	
	/**
	 * Instantiate an empty item for current table.
	 *
	 * @return  object
	 */
	public function fetch_new()
	{
		$finder = new XDao_Finder($this->schema());
		return $finder->fetch_new();
	}
	
	/**
	 * Creates a new item, from the associative array.
	 * Keys in array have to be column names in the table
	 *
	 * @param array $array 
	 * @return instance of XDao item (or extended class of it) or false on error
	 * @author Bogdan Stanescu
	 */
	public function create_from_array($array)
	{
		$item = $this->fetch_new();
		$item->from_array($array);
		if (!$item->save())
		{
			$item = false;
		}
		return $item;
	}
	
	/**
	 * Adds a new element in the current table
	 *
	 * @param mixed $vo value object (can be a stdClass) 
	 * @return void
	 * @author Bogdan Stanescu
	 */
	public function add_vo($vo)
	{
		$item = $this->fetch_new();
		$item->vo = $vo;
		return $item->save();
	}
	
	
	/**
	 * Instantiates a Finder object for current model.
	 *
	 * @return  object
	 */
	public function find()
	{
		return new XDao_Finder($this->schema());
	}
	
	/**
	 * Returns the schema for current object. Also caches it locally.
	 *
	 * @return object
	 */
	public function schema()
	{
		// Table name must be set
		if (!isset($this->table)) self::trigger_error(self::ERR_TABLE_NOT_SET);

		if (isset($this->schemas[$this->table])) return $this->schemas[$this->table];
		
		// Store table name
		$schema = new stdClass;
		$schema->table = $this->table;

		// Store table prefix
		if (isset($this->prefix))
		{
			$schema->prefix = $this->prefix;
		}

		// value object class name
		if (isset($this->vo))
		{
			$schema->vo = $this->vo;
		}
		// item object class name
		if (isset($this->item))
		{
			$schema->item = $this->item;
		}

		// Check if table has custom DB settings
		if (isset($this->db))
		{
			$schema->db = $this->db;
		}

		// Check table relationships
		if (isset($this->relations))
		{
			$schema->relations = $this->relations;
		}

		// Primary field, set to NULL as default, it will be auto-detected based on column settings
		$schema->primary = NULL;

		if (!isset($schema->columns))
		{
			if (!$schema->columns = $this->db->Query('DESC ' . $schema->table)) 
			{
				self::trigger_error(self::ERR_SCHEMA_NOT_FETCHED, $schema->table);
			}
		}

		// Stores columns information
		if ($schema->columns)
		{
			foreach ($schema->columns as $key => $column)
			{
				$nice_name = $column['Field'];

				// If prefix is set, determin nice internal name
				if ($schema->prefix)
				{
					if (substr($nice_name, 0, strlen($schema->prefix)) == $schema->prefix)
					{
						$nice_name = substr($nice_name, strlen($schema->prefix));
					}
				}

				// Determin column type
				$column_type = $column['Type'];
				if (FALSE !== strpos($column['Type'], '('))
				{
					$column_type = substr($column['Type'], 0, strpos($column['Type'], '('));
				}
				
				$schema->columns[$nice_name] = (object) array
				(
					'name' => $column['Field'],
					'type' => $column_type,
					'key' => $column['Key'],
					'null' => $column['Null'] == 'YES' ? TRUE : FALSE,
					'values' => rtrim(ltrim(substr($column['Type'], strpos($column['Type'], '(')), '('), ')')
				);
				unset($schema->columns[$key]);

				$schema->reverse_columns[$column['Field']] = $nice_name;
				
				// Check if field is primary
				if ($column['Key'] == 'PRI')
				{
					$schema->primary = $nice_name;
				}

			} // foreach ($schema->columns as $key => $column)

			if (is_null($schema->primary)) self::trigger_error(self::ERR_PRIMARY_KEY_NOT_DETECTED, $schema->table);

		} // if ($schema->columns)
		
		$this->schemas[$this->table] = $schema;

		return $schema;
	}

	

} // class XDao_Model

?>